import fs from "fs";
import * as anchor from "@coral-xyz/anchor";
import { PublicKey, SystemProgram, Keypair } from '@solana/web3.js';
import * as spl from '@solana/spl-token';
import { MessageTransmitter } from '../target/types/message_transmitter';
import { TokenMessengerMinter } from '../target/types/token_messenger_minter';
interface FindProgramAddressResponse {
publicKey: anchor.web3.PublicKey;
bump: number;
}
// Helper function to find PDAs (https://www.anchor-lang.com/docs/pdas)
const findProgramAddress = (
label: string,
programId: PublicKey,
extraSeeds: (string | number[] | Buffer | PublicKey)[] = null
): FindProgramAddressResponse => {
const seeds = [Buffer.from(anchor.utils.bytes.utf8.encode(label))];
if (extraSeeds) {
for (const extraSeed of extraSeeds) {
if (typeof extraSeed === "string") {
seeds.push(Buffer.from(anchor.utils.bytes.utf8.encode(extraSeed)));
} else if (Array.isArray(extraSeed)) {
seeds.push(Buffer.from(extraSeed as number[]));
} else if (Buffer.isBuffer(extraSeed)) {
seeds.push(extraSeed);
} else {
seeds.push(extraSeed.toBuffer());
}
}
}
const res = PublicKey.findProgramAddressSync(seeds, programId);
return { publicKey: res[0], bump: res[1] };
};
const hexToBytes = (hex: string): number[] => {
    const bytes: number[] = [];
    for (let c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16));
    return bytes;
};
const evmAddressToBytes32 = (address: string): string =>
    `0x000000000000000000000000${address.replace("0x", "")}`;

const decodeNonceFromMessage = (message: string): bigint => {
    const messageBytes = Buffer.from(message.replace("0x", ""), "hex");
    const nonceIndex = 12;
    const nonceBytesLength = 8;
    const eventNonceBytes = messageBytes.subarray(nonceIndex, nonceIndex +
    nonceBytesLength);
    const eventNonceHex = `0x${eventNonceBytes.toString("hex")}`;
    return BigInt(eventNonceHex);
};

// main script
const main = async () => {
    // Configure client to use the provider.
    // Must set ANCHOR_WALLET (solana keypair path) and ANCHOR_PROVIDER_URL (node URL) env vars
    const provider = anchor.AnchorProvider.env();
    anchor.setProvider(provider);
    // Initialize contracts
    const messageTransmitterProgram = anchor.workspace.MessageTransmitter as
    anchor.Program<MessageTransmitter>;
    const tokenMessengerMinterProgram = anchor.workspace.TokenMessengerMinter as
    anchor.Program<TokenMessengerMinter>;
    // Keypair and variables
    const userKeypair = Keypair.fromSecretKey(
    Uint8Array.from(JSON.parse(fs.readFileSync("local_key.json", "utf8")))
    );
    const usdcAddress = new
    PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
    // Default to AVAX
    const remoteTokenAddress = evmAddressToBytes32(process.env.REMOTE_TOKEN_HEX ??
    "0x5425890298aed601595a70ab815c96711a31bc65");
    // Default to AVAX
    const remoteDomain = process.env.REMOTE_DOMAIN ?? "1";
    const userTokenAccount = new PublicKey(process.env.USER_TOKEN_ACCOUNT);
    const messageHex = process.env.MESSAGE_HEX;
    const attestationHex = process.env.ATTESTATION_HEX;
    const nonce = decodeNonceFromMessage(messageHex);
    // Find pdas
    const messageTransmitterAccount = findProgramAddress("message_transmitter",
    messageTransmitterProgram.programId);
    const tokenMessenger = findProgramAddress("token_messenger",
    tokenMessengerMinterProgram.programId);
    const tokenMinter = findProgramAddress("token_minter",
    tokenMessengerMinterProgram.programId);
    const localToken = findProgramAddress("local_token",
    tokenMessengerMinterProgram.programId, [usdcAddress]);
    const remoteTokenMessengerKey = findProgramAddress("remote_token_messenger",
    tokenMessengerMinterProgram.programId, [
    remoteDomain.toString(),
    ]);
    const remoteTokenKey = new PublicKey(hexToBytes(remoteTokenAddress));

    const tokenPair = findProgramAddress("token_pair",
       tokenMessengerMinterProgram.programId, [
       remoteDomain,
       remoteTokenKey,
    ]);
    const custodyTokenAccount = findProgramAddress("custody",
        tokenMessengerMinterProgram.programId, [
        usdcAddress,
    ]);
const authorityPda = findProgramAddress(
"message_transmitter_authority",
messageTransmitterProgram.programId
).publicKey;
// Calculate the nonce PDA.
const maxNoncesPerAccount = 6400;
const firstNonce = ((nonce - BigInt(1)) / BigInt(maxNoncesPerAccount)) *
BigInt(maxNoncesPerAccount) + BigInt(1);
const usedNonces = findProgramAddress("used_nonces",
    messageTransmitterProgram.programId, [
    remoteDomain.toString(),
    firstNonce.toString(),
]).publicKey;
// Build the accountMetas list. These are passed as remainingAccounts for the TokenMessengerMinter CPI
const accountMetas = [];
accountMetas.push({
    isSigner: false,
    isWritable: false,
    pubkey: tokenMessenger.publicKey,
});
accountMetas.push({
isSigner: false,
isWritable: false,
pubkey: remoteTokenMessengerKey.publicKey,
});
accountMetas.push({
isSigner: false,
isWritable: true,
pubkey: tokenMinter.publicKey,
});
accountMetas.push({
isSigner: false,

isWritable: true,
pubkey: localToken.publicKey,
});
accountMetas.push({
isSigner: false,
isWritable: false,
pubkey: tokenPair.publicKey,
});
accountMetas.push({
isSigner: false,
isWritable: true,
pubkey: userTokenAccount,
});
accountMetas.push({
isSigner: false,
isWritable: true,
pubkey: custodyTokenAccount.publicKey,
});
accountMetas.push({
isSigner: false,
isWritable: false,
pubkey: spl.TOKEN_PROGRAM_ID,
});
// Call receiveMessage to mint the USDC
// userKeypair must have SOL for gas fees
const receiveMessageTx = await messageTransmitterProgram.methods
// receiveMessage parameters
.receiveMessage({
message: Buffer.from(messageHex.replace("0x", ""), "hex"),
attestation: Buffer.from(attestationHex.replace("0x", ""), "hex"),
})
// Add all needed accounts
.accounts({
    caller: userKeypair.publicKey,
    authorityPda,
    messageTransmitter: messageTransmitterAccount.publicKey,
    usedNonces,
    receiver: tokenMessengerMinterProgram.programId,
    systemProgram: SystemProgram.programId,
})
// Add remainingAccounts needed for TokenMessengerMinter CPI
.remainingAccounts(accountMetas)
.signers([userKeypair])
.rpc();
console.log("ReceiveMessage txHash: ", receiveMessageTx);
}
main();